package Discovery;
import Commons.Address;
import Commons.ComandoWorker;
import Commons.Indirizzi;
import Commons.Logging.Log;
import Commons.Logging.LogStatoCoda;
import Commons.TaskDescriptor;
import Commons.Logging.Logger;
import Commons.Logging.LoggerContenutoCode;
import Commons.Utility;
import Commons.Replicazione.RMDiscovery;
import Commons.Replicazione.StatoDiscovery;
import Commons.WorkerDescriptor;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.xml.stream.XMLStreamException;


/**Questa classe descrive il Worker Discovery.
 *
 * @author Team5
 */
public class WorkerDiscovery implements Discovery{

    private ArrayList <WorkerDescriptor>workers;

    

    public int getNumeroTotaleElementiInseriti() {
        return NumeroTotaleElementiInseriti;
    }

    public void setNumeroTotaleElementiInseriti(int NumeroTotaleElementiInseriti) {
        this.NumeroTotaleElementiInseriti = NumeroTotaleElementiInseriti;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Logger getL() {
        return l;
    }

    public void setL(Logger l) {
        this.l = l;
    }

    public LoggerContenutoCode getLcq() {
        return lcq;
    }

    public void setLcq(LoggerContenutoCode lcq) {
        this.lcq = lcq;
    }

    public Logger getLq() {
        return lq;
    }

    public void setLq(Logger lq) {
        this.lq = lq;
    }
    /**
     * Oggetto per la gestione del logging dello stato del worker
     */
    private Logger l;
    /**
     * Oggetto per la gestione del logging dello stato dela coda
     */
    private Logger lq;
    /**
     * Oggetto per la gestione del logging del contenuto della coda
     */
    private LoggerContenutoCode lcq;

    /**
     * ID della coda
     */
    private String id;
    /**
     * Costruttore di default
     */

    /**
     * Numero totale degli elementi inseriti in coda
     */
    private int NumeroTotaleElementiInseriti;

    public WorkerDiscovery(RMDiscovery rd,StatoDiscovery sd) throws XMLStreamException, InterruptedException {
     id=sd.getId();
        l=new Logger("log"+this.id+".xml",this.getClass().getSimpleName(),sd.getVectorClockl(),sd.getPosizionedelvectorClockl());
        workers=sd.getWorkers();
        lq=new Logger("logQueueWorkers"+this.id+".xml",this.workers.getClass().getSimpleName());
        this.lcq=new LoggerContenutoCode("logContenutoCode"+id+".xml");
        this.NumeroTotaleElementiInseriti=sd.getNumeroTotaleElementiInseriti();
        System.out.println("WorkDiscovery(): Avvio Server WorkerDiscovery");
        l.logging(new Log(this.getClass().getSimpleName(),id,"Avvio Server WorkerDiscovery",String.valueOf(System.currentTimeMillis())));
        GestioneWorker rw=new GestioneWorker();
        FornisciWorker fw=new FornisciWorker();
        //workers = new ArrayList<WorkerDescriptor>();



        Thread t1=new Thread(rw);
        Thread t2=new Thread(fw);
        t1.start();
        t2.start();
    }
    
    public WorkerDiscovery(RMDiscovery rd) throws XMLStreamException, InterruptedException {
      id=this.getClass().getSimpleName()+this.hashCode();
        l=new Logger("log"+this.id+".xml",this.getClass().getSimpleName());
        workers=new ArrayList <WorkerDescriptor>();
        lq=new Logger("logQueueWorkers"+this.id+".xml",this.workers.getClass().getSimpleName());
        this.lcq=new LoggerContenutoCode("logContenutoCode"+id+".xml");
        this.NumeroTotaleElementiInseriti=0;
        System.out.println("WorkDiscovery(): Avvio Server WorkerDiscovery");
        l.logging(new Log(this.getClass().getSimpleName(),id,"Avvio Server WorkerDiscovery",String.valueOf(System.currentTimeMillis())));
        GestioneWorker rw=new GestioneWorker();
        FornisciWorker fw=new FornisciWorker();
        workers = new ArrayList<WorkerDescriptor>();
        

        
        Thread t1=new Thread(rw);
        Thread t2=new Thread(fw);
        t1.start();
        t2.start(); 
    }
    
    /**
     *
     * @return
     */
    public ArrayList<WorkerDescriptor> getWorkers(){
        return this.workers;
    }

    /**
     *
     * @param val
     */
    public void setWorkers(ArrayList <WorkerDescriptor> val) {
        this.workers=val;
    }

    /**Il metodo riceve un td e restituisce l'Address del Worker che dovrà servirlo. Il metodo è implementato per fornire un Worker random fra quelli presenti.
     *
     * @param val TaskDescriptor da servire.
     * @return indirizzo del Worker che servirà quel TD.
     */   
    public Address getWorker(TaskDescriptor td) {
        //Simuliamo Che al momento il Worker sia unico
        try{
            l.merge(td.getVectorClock());
            int i=(int) (Math.random())*this.workers.size();
            Address a=new Address();
            WorkerDescriptor wd=this.workers.get(i);

            a.setIP(wd.getAddr().getIP());
            a.setPort(wd.getAddr().getPort());
            l.logging(new Log(this.getClass().getSimpleName(),id,"GetWorker sul Worker Descriptor "+td.toString(),String.valueOf(System.currentTimeMillis())));
            a.setVectorClock(l.getVectorClock());
            return a;
        }catch(Exception ex){
            //se arrivo qui vuol dire che la lista dei worker era vuota
            ex.printStackTrace();
            System.out.println("Nessun Worker disponibile.");
            return null;
        }
    }

    /**Il metodo consente di aggiungere un Worker alla lista di worker presenti nel WOrkerDiscovery.
     *
     * @param wd WorkerDescriptor del Worker da aggiungere.
     * @return posizione in cui è stato aggiunto il worker.
     */
    public int addworker(WorkerDescriptor wd) {
        System.out.println("DISCOVERY: ho aggiunto il Worker.");
        workers.add(wd);
        this.NumeroTotaleElementiInseriti++;
        try {
            lq.logging(new LogStatoCoda(this.getClass().getSimpleName(), id, "Aggiunto in coda " + wd.toString(), String.valueOf(System.currentTimeMillis()), this.NumeroTotaleElementiInseriti, this.workers.size()));
            lcq.logging(workers);
        } catch (InterruptedException ex) {
            java.util.logging.Logger.getLogger(WorkerDiscovery.class.getName()).log(Level.SEVERE, null, ex);
        }
        return workers.indexOf(wd);
    }

    public String getID(){
        return this.id;
    }


    public int updateWorker(int index, WorkerDescriptor wd) {
       System.out.println("DISCOVERY: ho aggiornato il Worker.");
        try {
            l.logging(new Log(this.getClass().getSimpleName(), id, "Update Worker" + wd.toString(), String.valueOf(System.currentTimeMillis())));
        } catch (InterruptedException ex) {
            java.util.logging.Logger.getLogger(WorkerDiscovery.class.getName()).log(Level.SEVERE, null, ex);
        }
       workers.add(index, wd);
       return 1;
    }

    

      private class GestioneWorker implements Runnable{

        private int portaDelDiscoveryGestioneWorkers=16000;
        public void run() {
            Utility u=new Utility();

           try
           {
              
              Utility u2=new Utility();
              int index=-1;
              u.inizializzaServerRicezioneUDP(portaDelDiscoveryGestioneWorkers);
              l.logging(new Log(this.getClass().getSimpleName(),id,"Inizializzazione Server Ricezione UDP sulla porta "+portaDelDiscoveryGestioneWorkers,String.valueOf(System.currentTimeMillis())));

              while (true)
              {
                  ComandoWorker cw =(ComandoWorker) u.riceviOggettoSerializzatoUDP();
                  if(cw.getComando().equals("Aggiungi")){
                    l.logging(new Log(this.getClass().getSimpleName(),id,"Aggiunto Worker "+cw.getWd().toString(),String.valueOf(System.currentTimeMillis())));
                    index=WorkerDiscovery.this.addworker(cw.getWd());}
                  else{
                   index=WorkerDiscovery.this.updateWorker(cw.getIndex(),cw.getWd());
                   Thread.sleep(100);
                   u2.invioOggettoSerializzatoUDP(index, u.getMittente(),16500);
                   l.logging(new Log(this.getClass().getSimpleName(),id,"Invio Oggetto Serializzato "+index,String.valueOf(System.currentTimeMillis())));
                   u2.chiudiSocket();
                  }
              }
             
              
                 

            

           }

           catch(Exception e)
           {
           }
           finally{
               u.chiudiSocket();
           }
        }
    }

    private class FornisciWorker implements Runnable{

        public void run(){
        Utility u=new Utility();
        Utility u2=new Utility();

        try {
            System.out.println("WD: inizializzo Server WorkerDiscovery per ricezione td");
             l.logging(new Log(this.getClass().getSimpleName(),id,"Inizializzazione Server WorkerDiscover per ricezione di TD",String.valueOf(System.currentTimeMillis())));
            u.inizializzaServerRicezioneUDP(5556);
            TaskDescriptor td;
            Address a;
            //riceve il TaskDescriptor
            while(true){
                System.out.println("WD: in attesa");
                td=(TaskDescriptor) u.riceviOggettoSerializzatoUDP();
                System.out.println("WD: ricevuto td "+td.getID());
                l.logging(new Log(this.getClass().getSimpleName(),id,"Ricevuto task descriptor "+td.toString(),String.valueOf(System.currentTimeMillis())));
                //genera l'Address del Worker che servirà quel TaskDescriptor
                a=getWorker(td);
                System.out.println("WD: generato Address. Invio al WorkerDiscoveryProxy");
                //invia l'Address
                Thread.sleep(100);
                u2.invioOggettoSerializzatoUDP(a,InetAddress.getByName(Indirizzi.indirizzoManager),5555);
            }


        } catch (Exception ex) {
          ex.printStackTrace();
        }
    }


    }
}
